package multipart

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"time"
)

type Manager struct {
	tmpDir string
	// 其他字段如 metaStore
}

type CompletePart struct {
	PartNumber int
	ETag       string
}

type UploadMeta struct {
	Bucket    string    `json:"bucket"`
	Key       string    `json:"key"`
	Initiated time.Time `json:"initiated"`
}

type PartInfo struct {
	PartNumber   int
	ETag         string
	Size         int64
	LastModified time.Time
}

type UploadInfo struct {
	UploadID  string
	Bucket    string
	Key       string
	Initiated time.Time
}

func NewManager(tmpDir string) *Manager {
	return &Manager{tmpDir: tmpDir}
}

// Initiate 创建上传工作区并返回 uploadID
func (m *Manager) Initiate(bucket, key string) (string, error) {
	if err := os.MkdirAll(m.tmpDir, 0755); err != nil {
		return "", err
	}
	uploadID := fmt.Sprintf("%d-%x", time.Now().UnixNano(), md5.Sum([]byte(bucket+"/"+key+fmt.Sprint(time.Now().UnixNano()))))
	dir := filepath.Join(m.tmpDir, uploadID)
	if err := os.MkdirAll(filepath.Join(dir, "parts"), 0755); err != nil {
		return "", err
	}
	meta := UploadMeta{Bucket: bucket, Key: key, Initiated: time.Now().UTC()}
	if b, _ := json.Marshal(meta); b != nil {
		_ = os.WriteFile(filepath.Join(dir, "meta.json"), b, 0644)
	}
	return uploadID, nil
}

// UploadPart 将分片写入工作区并返回该分片的 ETag 与大小
func (m *Manager) UploadPart(uploadID string, partNumber int, r io.Reader) (string, int64, error) {
	if partNumber <= 0 {
		return "", 0, fmt.Errorf("invalid part number")
	}
	partPath := filepath.Join(m.tmpDir, uploadID, "parts", fmt.Sprintf("%d", partNumber))
	if err := os.MkdirAll(filepath.Dir(partPath), 0755); err != nil {
		return "", 0, err
	}
	f, err := os.Create(partPath)
	if err != nil {
		return "", 0, err
	}
	defer f.Close()
	h := md5.New()
	n, err := io.Copy(io.MultiWriter(f, h), r)
	if err != nil {
		return "", 0, err
	}
	etag := fmt.Sprintf("%x", h.Sum(nil))
	return etag, n, nil
}

// Complete 旧签名：保持兼容（写入到临时固定路径）
func (m *Manager) Complete(uploadID string, parts []CompletePart) (string, error) {
	// 兼容旧用法，实际项目应使用新版 CompleteTo
	tmp := filepath.Join(m.tmpDir, uploadID+".final")
	etag, _, err := m.CompleteTo(uploadID, tmp, parts)
	return etag, err
}

// CompleteTo 将分片拼接到 dstPath 并返回最终 ETag 与总大小
func (m *Manager) CompleteTo(uploadID string, dstPath string, parts []CompletePart) (string, int64, error) {
	if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
		return "", 0, err
	}
	tmpOut := dstPath + ".mpu.tmp"
	out, err := os.Create(tmpOut)
	if err != nil {
		return "", 0, err
	}
	defer out.Close()
	var etagParts []byte
	var total int64
	for _, p := range parts {
		pf := filepath.Join(m.tmpDir, uploadID, "parts", fmt.Sprintf("%d", p.PartNumber))
		f, err := os.Open(pf)
		if err != nil {
			return "", 0, err
		}
		h := md5.New()
		tee := io.TeeReader(f, io.MultiWriter(out, h))
		n, err := io.Copy(io.Discard, tee)
		f.Close()
		if err != nil {
			return "", 0, err
		}
		total += n
		etagParts = append(etagParts, h.Sum(nil)...)
	}
	final := fmt.Sprintf("%x-%d", md5.Sum(etagParts), len(parts))
	if err := out.Sync(); err != nil {
		return "", 0, err
	}
	if err := os.Rename(tmpOut, dstPath); err != nil {
		return "", 0, err
	}
	_ = os.RemoveAll(filepath.Join(m.tmpDir, uploadID))
	return final, total, nil
}

// Abort 删除 uploadID 的工作区
func (m *Manager) Abort(uploadID string) error {
	return os.RemoveAll(filepath.Join(m.tmpDir, uploadID))
}

func (m *Manager) ListParts(uploadID string, partNumberMarker, maxParts int) (parts []PartInfo, nextMarker int, truncated bool, err error) {
	dir := filepath.Join(m.tmpDir, uploadID, "parts")
	ents, err := os.ReadDir(dir)
	if err != nil {
		return nil, 0, false, err
	}
	var nums []int
	for _, e := range ents {
		if e.IsDir() {
			continue
		}
		if n, err := strconv.Atoi(e.Name()); err == nil {
			if n > partNumberMarker {
				nums = append(nums, n)
			}
		}
	}
	sort.Ints(nums)
	limit := maxParts
	if limit <= 0 || limit > 10000 {
		limit = 1000
	}
	for i, n := range nums {
		if len(parts) >= limit {
			truncated = true
			nextMarker = nums[i-1]
			break
		}
		p := filepath.Join(dir, fmt.Sprintf("%d", n))
		fi, err := os.Stat(p)
		if err != nil {
			continue
		}
		f, err := os.Open(p)
		if err != nil {
			continue
		}
		h := md5.New()
		sz, _ := io.Copy(h, f)
		f.Close()
		parts = append(parts, PartInfo{
			PartNumber:   n,
			ETag:         fmt.Sprintf("%x", h.Sum(nil)),
			Size:         sz,
			LastModified: fi.ModTime().UTC(),
		})
	}
	if !truncated && len(nums) > 0 {
		nextMarker = nums[len(nums)-1]
	}
	return
}

func (m *Manager) ListUploads(bucket, prefix string, maxUploads int) (uploads []UploadInfo, truncated bool, err error) {
	ents, err := os.ReadDir(m.tmpDir)
	if err != nil {
		return nil, false, err
	}
	limit := maxUploads
	if limit <= 0 || limit > 1000 {
		limit = 1000
	}
	for _, e := range ents {
		if !e.IsDir() {
			continue
		}
		id := e.Name()
		b, err := os.ReadFile(filepath.Join(m.tmpDir, id, "meta.json"))
		if err != nil {
			continue
		}
		var mu UploadMeta
		if json.Unmarshal(b, &mu) != nil {
			continue
		}
		if mu.Bucket != bucket {
			continue
		}
		if prefix != "" && (len(mu.Key) < len(prefix) || mu.Key[:len(prefix)] != prefix) {
			continue
		}
		uploads = append(uploads, UploadInfo{
			UploadID:  id,
			Bucket:    mu.Bucket,
			Key:       mu.Key,
			Initiated: mu.Initiated,
		})
		if len(uploads) >= limit {
			truncated = true
			break
		}
	}
	return
}
